/****************************************************************************
 * Copyright (C) 2009-2010 SciTouch LLC
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#ifndef __molecule_h__
#define __molecule_h__

#include "base_c/defs.h"
#include "base_cpp/array.h"
#include "base_cpp/auto_ptr.h"
#include "base_cpp/obj.h"
#include "math/algebra.h"
#include "graph/graph.h"
#include "graph/graph_highlighting.h"
#include "molecule/elements.h"
#include "molecule/molecule_rgroups.h"
#include "molecule/molecule_cis_trans.h"
#include "molecule/molecule_stereocenters.h"

enum
{
   MAX_ATOM_LIST_SIZE = 10
};

class Molecule3dConstraints;

enum
{
   BOND_ZEROED = 0,  // Bond can appear during tautomerism
   BOND_SINGLE = 1,
   BOND_DOUBLE = 2,
   BOND_TRIPLE = 3,
   BOND_AROMATIC = 4,
   QUERY_BOND_SINGLE_OR_DOUBLE = 5,
   QUERY_BOND_SINGLE_OR_AROMATIC = 6,
   QUERY_BOND_DOUBLE_OR_AROMATIC = 7,
   QUERY_BOND_ANY = 8
};

enum
{
   QUERY_BOND_TOPOLOGY_RING   = 1,
   QUERY_BOND_TOPOLOGY_CHAIN  = 2
};

struct Atom
{
   explicit Atom ();

   void clear ();

   void copy (const Atom &other);

   bool pureHydrogen () const {return label == ELEM_H && isotope == 0;}

   int   label;
   int   valence;
   bool  explicit_valence;
   int   radical;
   int   isotope;
   int   implicit_h;
   int   charge;
   Vec3f pos;
};

enum
{
   QUERY_ATOM_A = 1,
   QUERY_ATOM_Q = 2,
   QUERY_ATOM_X = 3,
   QUERY_ATOM_LIST = 4,
   QUERY_ATOM_NOTLIST = 5,
   QUERY_ATOM_RGROUP = 6
};

enum
{
   QUERY_ATOM_AROMATIC = 1,
   QUERY_ATOM_ALIPHATIC = 2
};

struct QueryAtom
{
   explicit QueryAtom (int type_);

   void clear ();
   
   int  type; // 0 if not query atom, otherwise QUERY_ATOM_*
   int  hydrogens; // 1 means H0, 2 means H1, and so on
   int  aromaticity; // 0 or QUERY_ATOM_AROMATIC or QUERY_ATOM_ALIPHATIC
   int  substitution_count; // 0 -- unspecified, -1 -- zero, -2 -- as drawn, 1..5 -- 1..5, 6 -- 6 or more
   bool unsaturation;
   int  ring_bond_count;

   void addToList (int label);
   bool listContains (int label) const;

   int list[MAX_ATOM_LIST_SIZE];
   int list_size;
};

struct Bond
{
   explicit Bond (int type_) : type(type_) {}

   int type;        // see BOND_***
};

struct QueryBond
{
   explicit QueryBond (int type_);

   void clear ();
   
   int  type;              // 0 or QUERY_BOND_***
   int  topology;          // 0 or QUERY_BOND_TOPOLOGY_***
   int  stereo_care;       // care or not about cis-trans stuff
   bool can_be_aromatic;   // true if bond can be aromatic in any cycle 
};

class Molecule : public Graph
{      
public:
   explicit Molecule ();
   virtual ~Molecule ();

   virtual void clear ();

   void makeSubmolecule (const Molecule &other, const Array<int> &vertices, Array<int> *mapping_out);

   void makeSubmolecule (const Molecule &other, const Filter &filter, Array<int> *mapping_out, Array<int> *inv_mapping);

   void makeEdgeSubmolecule (const Molecule &molecule, const Array<int> &vertices, const Array<int> &edges, Array<int> *v_mapping);

   void makeSubmoleculeFromQuery (const Molecule &query, Array<int> *mapping_out, Array<int> *inv_mapping_out);

   void cloneMolecule (const Molecule &other, Array<int> *mapping, Array<int> *inv_mapping);
      
   static bool matchAtomsEq (const Graph &g1, const Graph &g2,
                              int idx1, int idx2, void *userdata);
   static int matchAtomsCmp (const Graph &g1, const Graph &g2,
                              int idx1, int idx2, void *userdata);
   static bool matchBondsEq (const Graph &g1, const Graph &g2,
                              int idx1, int idx2, void *userdata);

   int addBond (int atom_beg, int atom_end, int type);
   int addAtom (int label);
   int addBondCopy (int atom_beg, int atom_end, const Bond &bond);
   int addAtomCopy (const Atom &atom);
   void registerBond (int e_idx, const Bond &bond);
   void registerAtom (int v_idx, const Atom &atom);

   // Replace bond (atom_parent, atom_from) by (atom_parent, atom_to)
   // with updating stereocenters
   void flipBond (int atom_parent, int atom_from, int atom_to);

   const Atom & getAtom       (int idx) const;
         Atom & getAtom2      (int idx);
   const Bond & getBond       (int idx) const;
         Bond & getBond2      (int idx);
   void         setBondType   (int idx, int type);
   void         setAtom       (int idx, const Atom &atom);
   void         setAtomCharge (int idx, int charge);
   void         setPseudoAtom (int idx, const char *text);

   const char * getPseudoAtom (int idx) const;
   bool          isPseudoAtom (int idx) const;

   const Vec3f & getAtomPos (int idx) const;
   void          setAtomPos (int idx, const Vec3f &new_pos);
   
   void setBondAromatic (int idx);

   int getVacantPiOrbitals (int atom_idx, int *lonepairs_out) const;
   int getVacantPiOrbitals (int atom_idx, int conn, int *lonepairs_out) const;

   void calcImplicitHydrogens (bool calc_maximum = false);
   void calcConnectivity (bool to_throw);
   void calcExplicitValencesByImplicitHydrogens ();

   void mergeWithMolecule (const Molecule &other, Array<int> *p_mapping);

   void makeTranspositionForSubstructure (Array<int> &transposition_out) const;

   int totalCharge () const;
   int totalHydrogensCount () const;

   bool atomInAromaticRing (int idx) const;
   bool bondInRing (int idx) const;
   void findBondsInRings ();
   void setBondInRing (int idx, bool in_ring);

   bool have_xyz;

   static bool haveZCoord (const Molecule &mol);

   Molecule3dConstraints *create3dConstraints ();
   MoleculeStereocenters & getStereocenters () const; 
   Molecule3dConstraints *get3dConstraints () const;

   MoleculeRGroups        *createRGroups ();
   MoleculeRGroupFragment *createRGroupFragment ();
   MoleculeRGroups        *getRGroups () const;
   MoleculeRGroupFragment *getRGroupFragment () const;

   void clearRGroupFragment ();

   MoleculeCisTrans cis_trans;

   Array<int> fixed_atoms;

   void removeAtom (int idx);
   void removeAtoms (const Array<int> &indices);
   void removeAtoms (const Filter &filter);

   void removeBonds (const Array<int> &indices);
   void removeBond (int idx);

   void unfoldHydrogens (Array<int> *markers_out);

   void createQueryAtoms ();
   void createQueryBonds ();
   
   const QueryAtom & getQueryAtom (int idx) const;
   const QueryBond & getQueryBond (int idx) const;
   
   QueryAtom & getQueryAtom2 (int idx);
   QueryBond & getQueryBond2 (int idx);
   
   void setQueryBondAromatic (int idx);
   void setQueryBondFuzzyAromatic (int idx);

   bool haveQueryAtoms () const;
   bool haveQueryBonds () const;

   static bool hasStereoCareBonds (const Molecule &query);
   static bool hasExplicitValences (const Molecule &query);

   static void saveBondTypes (const Molecule &mol, Array<int> &types);
   static void loadBondTypes (Molecule &mol, Array<int> &types);

   static bool fragmentHasQueryBonds (const Molecule &mol, const Filter *filter);
   static bool fragmentHasQueryBonds (const Molecule &mol, const Array<int> &edges);
   static bool fragmentHasQueryAtoms (const Molecule &mol, const Filter *filter);
   static bool fragmentHasQueryAtoms (const Molecule &mol, const Array<int> &vertices);

   DEF_ERROR("molecule");

   static int getStereocentersOffset ();

   Array<char> name;

protected:

   void _mergeWithSubmolecule (const Molecule &molecule, const Array<int> &vertices,
                               const Array<int> *edges, Array<int> *mapping_out);

   Obj<MoleculeStereocenters> _stereocenters;
   Molecule3dConstraints *_3d_constraints;

   AutoPtr<MoleculeRGroups> _rgroups;
   AutoPtr<MoleculeRGroupFragment> _rgroup_fragment;

   Array<Atom> _atoms;
   Array<Bond> _bonds;

   StringPool            _pseudo_atom_values;
   RedBlackMap<int, int> _pseudo_atoms;
   
   Obj< Array<QueryAtom> > _q_atoms;
   Obj< Array<QueryBond> > _q_bonds;

   Array<char> _atoms_in_aromatic_rings;
   Array<int>  _bonds_in_rings;
   
   Array<int> _connectivity;
   
private:
   Molecule (const Molecule &); // no implicit copy
};

#endif
